Skip to content

feat(spec): action param 的 options[] 声明逐选项 visibleWhen (#5016) - #6235

Merged
qq9340100 merged 2 commits into
mainfrom
claude/issue-5016-action-param-options-vocab
Aug 7, 2026
Merged

feat(spec): action param 的 options[] 声明逐选项 visibleWhen (#5016)#6235
qq9340100 merged 2 commits into
mainfrom
claude/issue-5016-action-param-options-vocab

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes #5016

执行 2026-08-06 维护者裁 B(2026-08-07 窗口改判进 v17)。前置的逐键 liveness 审计改变了落点:裁决把审计设为前置条件,审计跑完的结论是「不是整套复用 SelectOptionSchema,而是只开 visibleWhen 一个键」。下面第 1 节是审计证据,第 2 节是与硬约束有关的一处前提证伪,两处都请复核。


1. 逐键 liveness 审计(objectui 只读,未改一行 objectui 代码)

问的不是「这个键在 objectui 存不存在」,而是「一个 action param 的选项列表能不能走到它的读者」。对话框只拿选项列表建一个输入控件,提交完就把列表丢掉 —— 它没有「已存值展示」这个 surface。

声明在 从 action param 选项可达的读者 证据 处置
visibleWhen SelectOptionSchema SelectField / MultiSelectField / RadioField / CheckboxesField 四个控件均调 useCascadingOptionscore/src/evaluator/optionRules.ts:101 o?.visibleWhenFieldRulePredicate = string | { dialect?, source }fieldRules.ts:33)正好收 ExpressionInputSchema 的信封 开放
color SelectOptionSchema 读者只有已存值的展示渲染器:fields/src/index.tsx:1118,1133SelectCellRenderer(网格/详情徽章)、metadata-viewer.tsx:152 的状态机视图。四个输入控件无一读它 —— MultiSelectField.tsx:9 在本地 Option 上声明了 color? 却从不渲染 继续拒绝
default SelectOptionSchema ,且是层级写错 唯一读者 metadata-viewer.tsx:153 options.find((o) = o.default),读的是对象字段的选项。对话框参数的默认值走参数自己的 defaultValue,高一层 继续拒绝
icon 仓里任何 spec 形状都没有 objectui 全仓 icon 命中只有 WidgetRegistry.ts:173manifest.iconschema-builder.ts:348,均无关 不升级 C
disabled 同上 四个选项控件里每一个 disabled 都是字段级 props.disabledSelectField.tsx:140MultiSelectField.tsx:125RadioField.tsx:92CheckboxesField.tsx:126),没有逐选项的 不升级 C

所以 icon / disabled 按裁决「证据不足则不升」→ 未升级 C;default 按派单「实测无消费方则不进 extend 面」→ 未纳入。color 的审计结论与派单的预设不同:派单据 issue 正文认为它活("packages/fields 的 option?.color"),但那个读者是对象字段已存值的展示渲染器,action param 的选项列表到不了它。按同一条「不声明惰性键」的理由一并排除 —— 这是本 PR 唯一超出派单明文授权的判断,请复核(见文末 open question)。

实现上没有用 .extend()

裁决写的是 SelectOptionSchema.extend()。实测这条路会丢掉整套错误面strictObject 的 error map 闭包在基座{ surface, history, aliases, guidance } 上,knownKeys 也从基座的 shape 读(shared/strict-object.ts),而 .extend() 产生的 clone 不带新的 declaration(该文件 docblock 自述「a marker on the instance does not survive the clone .extend() make」)。照搬会把批 14 那套 icon / disabled / default 的指路文案、以及 optionValue / optionLabel / displayName 三条 alias 全部替换成 SelectOptionSchema 的表,surface 名也会变成 "this select option"。因此保留本 surface 自己的 strictObject,只把 visibleWhen定义(同一个 ExpressionInputSchema)接进来 —— 一套词汇的目标达成,错误面不倒退。

顺带两处必须同步:

  • 删掉 visibleWhenguidance 条目 —— guidance 只从 unrecognized_keys 这条路被查,声明键永远到不了,留着就是 shared/alias-integrity.test.ts 判定的死条目。
  • 接过 visible / showWhen 两条 alias —— 目标键现在这个 shape 接受了,符合 finding 12「never suggest a key the schema cannot accept」。

2. 前提证伪:硬约束的「同一个 PR」在这条路上不成立

裁决的硬约束是「必须与 resolveActionParamsnormaliseOptions 停止重建条目同一个 PR落地,否则会造出『声明了、门过了、渲染器收不到』的键」。实测两件事:

(a) normaliseOptions 不在本仓。 它只存在于 objectui packages/app-shell/src/utils/resolveActionParams.ts:228;本仓 packages/spec/src/ui/action.zod.ts 的两处命中是注释引用。一个 PR 跨不了两个仓库。

(b) 更重要的是,本次开放的这条路根本不经过它。 resolveActionParamnormaliseOptions 只作用在 param.options ?? normaliseOptions(field.options, …)半 —— 即从字段继承的列表。作者显式写在 param 上的 options内联分支options: param.options 逐字下沉,随后 ActionParamDialog.tsx:207 逐条 { ...o, label: pickLocalized(...) }(spread,保留额外键)、paramToField.ts options: param.options(原样)。所以 spec 一放开,作者写的 visibleWhen 今天就到得了渲染器,零 objectui 改动

硬约束想防的危害在本次落点上不会发生。normaliseOptions 的丢弃是真的,但它丢的是字段自己早已声明的逐选项词汇(今天 main 上每一个 field-backed select 参数都在丢),既早于本次改动也不受其影响 —— 那是一条独立的 objectui 缺陷,已另行立单。本 PR 的拒绝文案因此仍然刻意开「把参数改成 field-backed 去继承」这张药方。


3. 验证

  • 反向验证(方向事先声明为「双通道红」,实测吻合):把 visibleWhen 从 shape 上摘掉后 —— (i) 三条端到端用例转红,报 unrecognized_keys: ["visibleWhen"],因为 shape 是 strict,是「响亮拒绝」而非「静默剥离」);(ii) shared/alias-integrity.test.ts 的 "every alias target is a key the schema really accepts" 同时转红,2 条 —— 新接的两条 alias 指向了 shape 不接受的键(finding 12 通道)。恢复后全绿。
  • 端到端断言走真实入口:7 条新用例全部经 getMetadataTypeSchema('action')MetadataManager.validate / GET /api/v1/meta / Studio 表单用的那道门)与 ObjectSchema.actions[],且断言键在 parse 输出里活着到达{ dialect: 'cel', source: … }),不是只断言 success —— 只断言 success 在批 14 之前那个静默剥离的世界里同样会绿。
  • pnpm --filter @objectstack/spec test330 files / 8426 tests 全绿(含新增 7 条)。
  • pnpm --filter @objectstack/runtime test105 files / 1506 tests 全绿typecheck 绿(先 --filter '@objectstack/runtime^...' build 起依赖)。
  • check:generated 10/10 绿content/docs/references/ui/action.mdxgen:docs 重生成,未手改;authorable-surface / json-schema 零变化 —— 该产物记的是顶层 authorable 属性,不含逐选项键)。
  • 「不代跑」6 源审计整组绿:check:liveness / check:empty-state / check:skill-examples / check:variant-docs / check:exported-any / check:dual-source-exports
  • pnpm lint 绿;check:nul-bytes(5921 tracked,另做 grep -naP 自扫)/ check:doc-authoring / check:adr-anchors / check:spec-parsed-alias / check:docs-audit-scope / check:release-notes 全绿。

4. 边界

⛔ 未动 objectui 任何代码(只读审计)。⛔ bulk-action.zod.ts.passthrough() 原样保留 —— #4909 的两条理由在本条路上都不成立,正文已辨析。⛔ 未碰 content/docs/releases/。changeset 定 @objectstack/spec: major(随 v17 列车),FROM→TO 写明了行为激活面:16.x 里作者写的 visibleWhen 被静默剥掉、选项永远可选,17.0.0 起键保留并生效、选项集会变窄

5. 与在飞单的关系

Open question(需维护者一句话确认)

color 是否要一并开放?裁决说的是「复用 SelectOptionSchema」,审计测出它在这条路上无读者,我按「不声明惰性键」排除了。方向不对称:现在补开是加性的、一行的;先开了再收窄是破坏性的。所以本 PR 取可回退的那一侧,等一句确认。


Generated by Claude Code

批 14 把 ActionParamSchema.options[] 关成 { label, value } 并把能力问题
留给 #5016。逐键量过消费面后,只开 visibleWhen 一个键 —— 它是唯一一个
在 action param 这条路上真有读者的:内联参数的 options 逐字下沉
(resolveActionParam 内联分支 → ActionParamDialog → paramToField),四个
选项控件都经 useCascadingOptions → resolveCascadingOptions 按它过滤,
且 evalFieldPredicate 接受 ExpressionInputSchema 产出的 { dialect, source }
信封。此前挡在作者和这个能工作的门控之间的,只有 spec 这道门。

color / default 继续拒绝:前者只被"已存值"的展示渲染器读(网格单元格 /
详情徽章),对话框只拿列表建输入控件;后者是层级写错,参数的默认值走
高一层的 defaultValue。icon / disabled 也未升级进 SelectOptionSchema
(#5016 的 C 选项)—— 重测确认 objectui 无任何读者,四个控件里的
disabled 全是字段级 props.disabled。四个键各自保留指路的 guidance。

visibleWhen 的 guidance 条目必须移除(声明键到不了 unrecognized_keys
这条路,留着就是 alias-integrity 判定的死条目),并把 SelectOptionSchema
的两个拼法 visible / showWhen 作为 alias 接过来。

新测试全部走真实的门(getMetadataTypeSchema('action') 与
ObjectSchema.actions[]),并断言键在 parse 输出里"活着到达",而不只是
parse 成功 —— 只断言 success 在批 14 之前那个静默剥离的世界里同样会绿。

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 7, 2026 11:57am

Request Review

@github-actions github-actions Bot added the size/m label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/dogfood, @objectstack/spec.

113 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via packages/qa/dogfood, @objectstack/spec)
  • content/docs/permissions/delegated-administration.mdx (via packages/qa/dogfood)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…5016)

expression-conformance 的 ratchet 会重新扫描 packages/spec/src 下每个
ExpressionInputSchema 声明,新增的 ui/action.zod.ts:visibleWhen 没有归类,
CI 的 Dogfood Regression Gate (3/3) 因此报 "UNCLASSIFIED surface"。本仓
scoped 的 spec / runtime 测试看不到这一条 —— 它只在 dogfood 分片里跑。

单开一行而不是并进 cel-ui:cel-ui 那批是 SchemaRenderer 藏元素,这一条是
在字段控件内部收窄一个选项列表,evaluator 不同。tier 取 fail-soft-log,
与 cel-field-rule 一致 —— evalFieldPredicate 的 fallback 是 true,谓词坏掉
时选项保持可选,而不是无声删掉一个作者没打算拿走的选择。

Co-Authored-By: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

第二个提交(5dce674):CI 抓到一条本地 scoped 测试看不见的门。

首轮 CI 的 Dogfood Regression Gate (3/3) 红,是我的:

+ "UNCLASSIFIED surface — add a ledger row (ADR-0060): ui/action.zod.ts:visibleWhen"
❯ test/expression-conformance.test.ts:56:43

packages/qa/dogfood/test/expression-conformance.test.ts重新扫描 packages/spec/src 下每一个 ExpressionInputSchema 声明(正则 ^\s*(\w+)\s*:\s*ExpressionInputSchema),要求每条都被 ADR-0058 D7 账本恰好一行 covers。新增一个表达式面而不归类,就是它存在的理由(#1887 那类「声明了却没接线的谓词」)。这条门只在 dogfood 分片里跑 —— --filter @objectstack/spec test / --filter @objectstack/runtime test 都看不到它,本地全绿并不代表它绿。

修法是给 expression-conformance.ledger.ts 加一行 cel-action-param-option-visible

  • 单开一行,不并进 cel-ui —— cel-ui 那批是 SchemaRenderer 把一个元素藏掉,这一条是在字段控件内部收窄一个选项列表,evaluator 不是同一个(resolveCascadingOptions)。账本的每行都要求 enforcement 写清求值链路,混在一起就写不准。
  • tier 取 fail-soft-log,与 cel-field-rule 一致 —— evalFieldPredicate 在这条路上的 fallbacktrue,谓词坏掉时选项保持可选,而不是无声删掉一个作者没打算拿走的选择。取 fail-closed 会把账本写成一句假话。
  • 行里同时记下了这句边界:enforceActionParams(ADR-0104 D2)只按声明的选项校验提交,不求值这个谓词,所以访问控制必须由 action body / 权限再拒一次。

复推后 24 个 check 全部完成:Console Pin Gate skipped,其余全绿 —— 含 ESLint(家族门都在这个 job 里)、TypeScript Type CheckDogfood Regression Gate 三个分片、Test Core 三个分片、Check ChangesetSpec property liveness


Generated by Claude Code

@qq9340100
qq9340100 marked this pull request as ready for review August 7, 2026 12:26
@qq9340100
qq9340100 added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit f6609e6 Aug 7, 2026
25 checks passed
@qq9340100
qq9340100 deleted the claude/issue-5016-action-param-options-vocab branch August 7, 2026 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

action param 的 options[] 该不该讲字段级的逐选项词汇(color / visibleWhen)? —— 三处形状,三种拼法

1 participant